﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//using System.Diagnostics;

namespace MazeModel
{
    public enum CollDir
    {
        North,
        South,
        East,
        West
    }

    public delegate void Collect(int amount, int x, int y);
    public delegate void CompleteLevel ();
    public delegate void Die();
    public delegate void GameOver();

    public class MazeController
    {

        public Collect collect = delegate { };
        public CompleteLevel completeLevel = delegate { };
        public Die die = delegate { };
        public GameOver gameOver = delegate { };

        public int ticks = 0;
        public int level = 1;

        public MazeLayout mazeMaze = new MazeLayout(16, 16);

        public MazePlayer mazePlayer = new MazePlayer();
        public MazeObject mazeEnd = new MazeObject();

        // Þessi listi sem við ætlum að búa til núna mun innihalda alla þá MazeTreasure or MazeEnd og óvini sem
        // verða í notkun. Þá er auðvelt að sækla í gegnum þá alla til að teikna og gera aðra hluti.
        public List<MazeTreasure> mazeTreasures = new List<MazeTreasure>();         // List of treasures
        public List<MazeObject> mazeEnds = new List<MazeObject>();                  // List of endpoints
        public List<MazeObject> mazeEnemies = new List<MazeObject>();               // List of enemies
        public List<EnemyWallhugger> enemyWHlist = new List<EnemyWallhugger>();     // List of enemies of type Wallhugger

        public int lastScore;                   // Last score
        public int remainingTime { get; private set; }
        public int remainingLives { get; private set; }
        public int remainingBonus
        {
            get 
            { 
                return remainingTime - (remainingTime % 100); 
            }
            private set 
            {
                this.remainingBonus = value;
            }
        }

        public MazeController()
        {
            this.StartNewGame();
            this.die += this.ReportDeath;
            this.gameOver += this.ReportGameOver;
        }

        public void StartNewGame()
        {
            this.remainingLives = 3;
            this.level = 1;
            this.ticks = 0;
            mazePlayer.score = 0;
            mazeMaze.randomize(level);
        }

        private void ReportGameOver()
        {
            this.StartNewGame();
        }
        private void ReportDeath()
        {
            this.lastScore = mazePlayer.score;
            remainingLives--;
            if (remainingLives <= 0)
            {
                this.gameOver.Invoke();
            }
            else
            {
                this.Restart();
            }
        }

        // At the moment, this simply re-runs "prepare level"
        public void Restart()
        {
            this.PrepareLevel();
        }

        // Randomizes mazeMaze and prepares it
        public void randomizeMaze()
        {
            mazeMaze.randomize(level);
            this.PrepareLevel();
        }

        // Loads the text file level pathToFile
        public void loadLevel(string pathToFile)
        {
            mazeMaze.loadLevel(pathToFile);
            this.PrepareLevel();
        }

        // Prepares the level to be played
        // Resets the time limit and iterates through the maze and generates the required objects
        private void PrepareLevel()
        {
            remainingTime = CalculateTimelimit();
            mazeTreasures.Clear();
            mazeEnds.Clear();
            mazeEnemies.Clear();
            enemyWHlist.Clear();
            
            for (int i = 0; i != mazeMaze.getWidth(); i++)
            {
                for (int j = 0; j != mazeMaze.getWidth(); j++)
                {
                    if (mazeMaze.getCellAt(i, j) == 2)
                    {
                        mazePlayer = new MazePlayer(i, j, mazePlayer.score);
                    }
                    if (mazeMaze.getCellAt(i, j) == 3)
                    {
                        mazeEnds.Add(new MazeObject(i, j));
                    }
                    if (mazeMaze.getCellAt(i, j) == 4)
                    {
                        mazeTreasures.Add(new MazeTreasure(i, j, 100));
                    }
                    if (mazeMaze.getCellAt(i, j) == 5)
                    {
                        EnemyWallhugger enm = new EnemyWallhugger(i,j);
                        enemyWHlist.Add(enm);
                        mazeEnemies.Add(enm);
                    }
                }
            }
        }

        // Formula to calculate how much time will be allotted to the player for this maze
        // Will probably have to be tweaked a LOT
        private int CalculateTimelimit()
        {
            return 6 * mazeMaze.getWidth() * mazeMaze.getHeight();
        }

        // Þetta lætur MazeModel taka eitt "tick", þe einn plank tíma í leiknum. "dir" er input frá GUI (MazeForm í þetta skiptið) sem 
        // táknar í hvaða átt leikmaður er að ýta á á lyklaborði
        public void gameTick(int dir)
        {
            ticks++;
            remainingTime--;

            // sendi boð til mazePlayer (sm er Object af taginu MazePlayer sem erfir frá MazeObject) um að hreyfa sig í áttina dir
            // þetta boð sendir mazeMaze.getCellAdjacent(mazePlayer.getPos(), dir) með, sem er gildi sellunnar í áttina sem player 
            // ætlar að fara. MazePlayer ákveður hvort hann færi sig áfram. Þetta er nauðsynlegt vegna þess að það er ekki víst að MazePlayer
            // færist í næstu sellu (út af subposision)
            // MazePlayer ákveður sjálfur hvort hann færist í næstu sellu eða hvort hann færist aðeins áfram í núverandi sellu
            mazePlayer.move(mazeMaze.getCellAdjacent(mazePlayer.getPos(), dir), dir);

            // Make enemies react
            foreach (EnemyWallhugger enemy in enemyWHlist)
            {
                enemy.react(
                    mazeMaze.getCellAdjacent(enemy.pos, 1), 
                    mazeMaze.getCellAdjacent(enemy.pos, 2), 
                    mazeMaze.getCellAdjacent(enemy.pos, 3), 
                    mazeMaze.getCellAdjacent(enemy.pos, 4),
                    1,1); // Player position doesn't matter for this enemy
            }


            // Athuga hvort leikmaður sé á enda (MazeEnd) og láta hann vinna ef svo er
            for (int i = 0; i < mazeEnds.Count; i++)
            {
                if ((mazePlayer.getPos()[0] == mazeEnds[i].getPos()[0]) &&
                    mazePlayer.getPos()[1] == mazeEnds[i].getPos()[1])
                {
                    level++;
                    ticks = 0;
                    mazePlayer.score += remainingBonus;//(remainingTime - ((remainingTime % 100) + 100));
                    System.Console.WriteLine("remainingBonus:" + remainingBonus);
                    this.completeLevel.Invoke();
                }
            }
            // Hér er ákvarðað hvort mazePlayer sé staðsettur á sömu sellu og einhver mazeTreasure í listanum mazeTreasures
            // Ef svo er þá er sá hlutur fjarlægður úr listanum mazeTreasures
            for (int i = 0; i < mazeTreasures.Count; i++)
            {
                if ((mazePlayer.getPos()[0] == mazeTreasures[i].getPos()[0]) &&
                    mazePlayer.getPos()[1] == mazeTreasures[i].getPos()[1])
                {
                    // No more of this i, j, matrix nonsense. We will translate the i,j into coordionates before sending them through the delegate
                    mazePlayer.score += mazeTreasures[i].points;
                    this.collect.Invoke(mazeTreasures[i].points, mazeTreasures[i].pos[1] * 16, mazeTreasures[i].pos[0] * 16);
                    mazeTreasures.RemoveAt(i);
                }
            }
            for (int i = 0; i < mazeEnemies.Count; i++)
            {
                if ((mazePlayer.getPos()[0] == mazeEnemies[i].getPos()[0]) &&
                    mazePlayer.getPos()[1] == mazeEnemies[i].getPos()[1])
                {
                    // No more of this i, j, matrix nonsense. We will translate the i,j into coordionates before sending them through the delegate
                    this.die.Invoke();
                }
            }

            // Kill player if his time is up
            if (remainingTime <= 0)
            {
                this.die.Invoke();
            }

        }
    }
}
